/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see
 * <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.validator.engine;

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import ch.trackedbean.validator.*;

/**
 * Class describing an mapping between an external {@link Annotation} and an {@link IPropertyValidator}.<br>
 * Use this mapping to "resuse" existing {@link Annotation}s for validation purposes.
 * 
 * @param <T> The annotation type
 * @param <V> The type of the validator
 * 
 * @author M. Hautle
 */
public class ExternalAnnotationMapping<T extends Annotation, V extends IPropertyValidator> {
	/** The destination validator. */
	protected final Class<V> validator;

	/** The external annotation to map on {@link #validator}. */
	protected final Class<T> annotation;

	/** List holding the parameter mapping. */
	protected final List<Parameter> params = new ArrayList<Parameter>(1);

	/**
	 * Default constructor.
	 * 
	 * @param annotation The external annotation
	 * @param validator The target validator
	 */
	public ExternalAnnotationMapping(Class<T> annotation, Class<V> validator) {
		this.validator = validator;
		this.annotation = annotation;
	}

	/**
	 * Returns the type of the external annotation.
	 * 
	 * @return The type of the annotation
	 */
	public Class<T> getExternalAnnotationType() {
		return annotation;
	}

	/**
	 * Returns the type of the validator to use.
	 * 
	 * @return Returns the validator type
	 */
	public Class<V> getValidatorType() {
		return validator;
	}

	/**
	 * Adds a parameter mapping between the annotation and a logical name used by the validator.
	 * 
	 * @param srcParam The parameter name on the external annotation
	 * @param dstParam The parameter name as used by the validator
	 * @throws NoSuchMethodException If the annotation has no such method
	 */
	public void addParameter(String srcParam, String dstParam) throws NoSuchMethodException {
		params.add(new Parameter(annotation.getMethod(srcParam), dstParam));
	}

	/**
	 * Copies the values from the passed annoation into the given configuration.
	 * 
	 * @param annotation The annotation
	 * @param cfg The configuration
	 * @throws ExternalConfigurationException If something went wrong
	 */
	public void copyConfig(T annotation, Configuration cfg) throws ExternalConfigurationException {
		cfg.clear();
		for (Parameter p : params)
			p.copy(annotation, cfg);
	}

	/**
	 * Parameter mapping between a {@link ExternalAnnotationMapping#annotation} parameter and it's name in the specified
	 * {@link IPropertyValidator}.
	 * 
	 * @author M. Hautle
	 */
	protected static class Parameter {
		/** The parameter name used by the {@link IPropertyValidator}. */
		protected final String dstParam;

		/** The method to invoke on the {@link ExternalAnnotationMapping#annotation} to extract the value. */
		protected final Method srcParam;

		/**
		 * Default constructor
		 * 
		 * @param srcParam The source parameter
		 * @param dstParam The destination parameter name
		 */
		protected Parameter(Method srcParam, String dstParam) {
			this.srcParam = srcParam;
			this.dstParam = dstParam;
		}

		/**
		 * Copies the parameter from the given annotation into the passed configuration.
		 * 
		 * @param a The annotation
		 * @param cfg The config
		 * @throws ExternalConfigurationException If something went wrong
		 */
		protected void copy(Annotation a, Configuration cfg) throws ExternalConfigurationException {
			try {
				cfg.addEntry(dstParam, srcParam.invoke(a));
			} catch (InvocationTargetException e) {
				throw new ExternalConfigurationException(e);
			} catch (IllegalAccessException e) {
				throw new ExternalConfigurationException(e);
			}
		}
	}

	/**
	 * Excpetion thrown if an error occours during the configuration.
	 * 
	 * @author M. Hautle
	 */
	public static class ExternalConfigurationException extends Exception {
		/**
		 * Default constructor.
		 * 
		 * @param e The source exception
		 */
		public ExternalConfigurationException(Exception e) {
			super(e);
		}
	}
}
